#include "fdfs_api.h"
#include <stdio.h>
#include "make_log.h"
#include "mysql_conn.h"
#include "cfg.h"
#include <sys/time.h>
#include "cJSON.h"
#include "fcgi_stdio.h"
#include <string.h>
#include "utils.h"
#include "mysql_conn.h"
#include <stdlib.h>
#include <hiredis/hiredis.h>

/*--------------------------------------------------------
| 共享用户文件有序集合 (ZSET)
| Key:     FILE_PUBLIC_LIST
| value:   md5文件名
| redis 语句
|   ZADD key score member 添加成员
|   ZREM key member 删除成员
|   ZREVRANGE key start stop [WITHSCORES] 降序查看
|   ZINCRBY key increment member 权重累加increment
|   ZCARD key 返回key的有序集元素个数
|   ZSCORE key member 获取某个成员的分数
|   ZREMRANGEBYRANK key start stop 删除指定范围的成员
|   zlexcount zset [member [member 判断某个成员是否存在，存在返回1，不存在返回0
`---------------------------------------------------------*/
#define FILE_PUBLIC_ZSET                  "FILE_PUBLIC_ZSET"

/*-------------------------------------------------------
| 文件标示和文件名对应表 (HASH)
| Key:    FILE_NAME_HASH
| field:  file_id(md5文件名)
| value:  file_name
| redis 语句
|    hset key field value
|    hget key field
`--------------------------------------------------------*/
#define FILE_NAME_HASH                "FILE_NAME_HASH"

static char mysql_user[128] = {0};
static char mysql_pwd[128] = {0};
static char mysql_db[128] = {0};

static char redis_ip[32] = {0};
static char redis_port[32] = {0};

int get_json_info(char *buf, char *user, char *token, char *md5, char *filename);
int cancel_share_file(char *user, char *md5, char *filename);
int verify_user(char* user, char* md5);
int save_file(char* user, char* md5, char* filename);
int pv_file(char *md5, char *filename);

int rop_zset_exit(redisContext *conn, char *key, char *member);
int rop_zset_add(redisContext *conn, char* key, long score, char* member);
int rop_zset_zrem(redisContext *conn, char* key, char* member);
int rop_zset_increment(redisContext *conn, char* key, char* member);

int rop_hash_set(redisContext *conn, char *key, char *fileid, char *value);
int rop_hash_del(redisContext *conn, char *key, char *fileid);

int main()
{
    char cmd[20] = {0};
    char user[64] = {0};
    char token[256] = {0};
    char md5[256] = {0};
    char filename[256] = {0};

    read_cfg(mysql_user, mysql_pwd, mysql_db);
    get_cfg_value(CFG_PATH, "redis", "ip", redis_ip);
    get_cfg_value(CFG_PATH, "redis", "port", redis_port);

    while(FCGI_Accept() >= 0)
    {
        printf("Content-type: text/html; charset=utf8\r\n\r\n");

        char* query_string = getenv("QUERY_STRING");

        parse_get_params(query_string, "cmd", cmd, NULL);
        LOG("cmd = %s\n", cmd);

        char buf[4*1024] = {0};
        int ret = 0;
        ret = fread(buf, 1, sizeof(buf), stdin);
        LOG("buf: %s\n", buf);
        if(ret == 0)
        {
            LOG("fread error\n");
            continue;
        }

        get_json_info(buf, user, token, md5, filename);
        LOG("user = %s, token = %s, md5 = %s, file_name = %s\n", user, token, md5, filename);
   
        ret = verify_token(user, token);
        if(ret != 0)
        {
            char* out = return_status("111");
            if(out != NULL)
            {
                printf(out);
                free(out);
                out = NULL;
            }

            continue;
        }

        if(strcmp(cmd, "cancel") == 0)
        {
            ret = verify_user(user, md5);
            if(ret != 0)
            {
                continue;
            }
            cancel_share_file(user, md5, filename);
        }
        else if(strcmp(cmd, "save") == 0)
        {
            save_file(user, md5, filename);
        }
        else if(strcmp(cmd, "pv") == 0)
        {
            pv_file(md5, filename);
        }
    }
    return 0;
}

int verify_user(char* user, char* md5)
{
    int ret = 0;
    int ret2 = 0;
    MYSQL* conn = NULL;
    char sql_cmd[512] = {0};
    char* out = NULL;

    do
    {
        conn = get_mysql(mysql_user, mysql_pwd, mysql_db);
        if(conn == NULL)
        {
            LOG("get_mysql() error\n");
            ret = -1;
            break;
        }

        sprintf(sql_cmd, "select * from share_file_list where md5 = '%s' and user = '%s'", md5, user);
        ret2 = process_result_one(conn, sql_cmd, NULL);
        if(ret2 == 1)
        {
            LOG("不是此人的文件，无法取消分享");
            out = return_status("110");
            if(out != NULL)
            {
                printf(out);
                free(out);
                out = NULL;
            }
            ret = -1;
            break;
        }
    }while(0);

    return ret;
}

int get_json_info(char *buf, char *user, char *token, char *md5, char *filename)
{
    int ret = 0;
    cJSON* root = NULL;

    do
    {
        root = cJSON_Parse(buf);
        if(NULL == root)
        {
            LOG("cJSON_Parse error\n");
            ret = -1;
            break;
        }

        cJSON* user_js = cJSON_GetObjectItem(root, "user");
        if(NULL == user_js)
        {
            LOG("cJSON_GetObjectItem error\n");
            ret = -1;
            break;
        }

        cJSON* token_js = cJSON_GetObjectItem(root, "token");
        if(token_js == NULL)
        {
            LOG("cJSON_GetObjectItem error\n");
            ret = -1;
            break;
        }

        cJSON* md5_js = cJSON_GetObjectItem(root, "md5");
        if(NULL == md5_js)
        {
            LOG("cJSON_GetObjectItem error\n");
            ret = -1;
            break;
        }

        cJSON* filename_js = cJSON_GetObjectItem(root, "filename");
        if(NULL == filename_js)
        {
            LOG("cJSON_GetObjectItem error\n");
            ret = -1;
            break;
        }

        strcpy(user, user_js->valuestring);
        strcpy(token, token_js->valuestring);
        strcpy(md5, md5_js->valuestring);
        strcpy(filename, filename_js->valuestring);
    }while(0);

    if(root != NULL)
    {
        cJSON_Delete(root);
        root = NULL;
    }

    return ret;
}

int cancel_share_file(char *user, char *md5, char *filename)
{
    /*
    a)先判断此文件是否已经分享，判断集合有没有这个文件，如果有，说明别人已经分享此文件，中断操作(redis操作)
    b)如果集合没有此元素，可能因为redis中没有记录，再从mysql中查询，如果mysql也没有，说明真没有(mysql操作)
    c)如果mysql有记录，而redis没有记录，说明redis没有保存此文件，redis保存此文件信息后，再中断操作(redis操作)
    d)如果此文件没有被分享，mysql保存一份持久化操作(mysql操作)
    e)redis集合中增加一个元素(redis操作)
    f)redis对应的hash也需要变化 (redis操作)
    */
    int ret = 0;
    char sql_cmd[512] = {0};
    MYSQL *conn = NULL;
    redisContext * redis_conn = NULL;
    char *out = NULL;
    char fileid[1024] = {0};

    do
    {
        redis_conn = redisConnect(redis_ip, atoi(redis_port));
        if(redis_conn == NULL || redis_conn->err)
        {
            if(redis_conn)
            {
                LOG("%s\n", redis_conn->errstr);
            }
            else
            {
                LOG("redisConnect error\n");
            }
            ret = -1;
            break;
        }

        conn = get_mysql(mysql_user, mysql_pwd, mysql_db);
        if(conn == NULL)
        {
            LOG("get_mysql error\n");
            ret = -1;
            break;
        }

        mysql_query(conn, "set names utf8mb4");

        sprintf(fileid, "%s%s", md5, filename);

        sprintf(sql_cmd, "update user_file_list set shared_status = 0 where user = '%s' and md5 = '%s' and file_name = '%s'", user, md5, filename);
        if(mysql_query(conn, sql_cmd) != 0)
        {
            LOG("%s 操作失败：%s\n", sql_cmd, mysql_error(conn));
            ret = -1;
            break;
        }

        sprintf(sql_cmd, "select count from user_file_count where user = '%s'", "xxx_share_xxx_file_xxx_list_xxx_count_xxx");

        int count = 0;
        char tmp[512] = {0};
        int ret2 = process_result_one(conn, sql_cmd, tmp);
        if(ret2 != 0)
        {
            LOG("%s 操作失败: %s\n", sql_cmd, mysql_error(conn));
            ret = -1;
            break;
        }

        count = atoi(tmp);
        if(count == 1)
        {
            sprintf(sql_cmd, "delete from user_file_count where user = '%s'", "xxx_share_xxx_file_xxx_list_xxx_count_xxx");
        }
        else
        {
            sprintf(sql_cmd, "update user_file_count set count = %d where user = '%s'", count-1, "xxx_share_xxx_file_xxx_list_xxx_count_xxx");
        }

        if(mysql_query(conn, sql_cmd) != 0)
        {
            LOG("%s 操作失败: %s\n", sql_cmd, mysql_error(conn));
            ret = -1;
            break;
        }

        sprintf(sql_cmd, "delete from share_file_list where user = '%s' and md5 = '%s' and file_name = '%s'", user, md5, filename);
        if(mysql_query(conn, sql_cmd) != 0)
        {
            LOG("%s 操作失败：%s\n", sql_cmd, mysql_error(conn));
            ret = -1;
            break;
        }

        ret = rop_zset_zrem(redis_conn, FILE_PUBLIC_ZSET, fileid);
        if(ret != 0)
        {
            LOG("rop_zset_zrem 操作失败\n");
            break;
        }

        ret = rop_hash_del(redis_conn, FILE_NAME_HASH, fileid);
        if(ret != 0)
        {
            LOG("rop_hash_del 操作失败\n");
            break;
        }

    }while(0);

    /*
    取消分享：
        成功：{"code":"018"}
        失败：{"code":"019"}
    */
    out = NULL;
    if(ret == 0)
    {
        out = return_status("018");
    }
    else 
    {
        out = return_status("019");
    }

    if(out != NULL)
    {
        printf(out);
        free(out);
        out = NULL;
    }

    if(redis_conn != NULL)
    {
        redisFree(redis_conn);
        redis_conn = NULL;
    }

    if(conn != NULL)
    {
        mysql_close(conn);
        conn = NULL;
    }

    return ret;
}

/* -------------------------------------------*/
/**
 * @brief        判断某个成员是否存在
 *
 * @param conn      已建立的链接
 * @param key       zset表名
 * @param member    zset成员名
 *
 * @returns
 *            0            不存在
 *            1            存在
 *            -1           出错
 */
/* -------------------------------------------*/
int rop_zset_exit(redisContext *conn, char *key, char *member)
{
    int ret = 0;
    redisReply* reply = NULL;

    do
    {
        reply = redisCommand(conn, "zlexcount %s [%s [%s", key, member, member);

        if(reply->type != REDIS_REPLY_INTEGER)
        {
            LOG("[-][GMS_REDIS]zlexcount: %s,member: %s Error:%s,%s\n", key, member,reply->str, conn->errstr);
            ret = -1;
            break;
        }

        ret = reply->integer;
    }while(0);

    if(reply != NULL)
    {
        freeReplyObject(reply);
        reply = NULL;
    }
    return ret;
}

//==================有序集合相关操作====================

/* -------------------------------------------*/
/**
 * @brief        将指定的zset表，添加新成员
 *                （key 或 成员不存在 则创建）
 *
 * @param conn        已建立的链接
 * @param key         zset表名
 * @param score       zset分数(权重)
 * @param member      zset成员名
 *
 * @returns
 *            0            succ
 *            -1           fail
 */
/* -------------------------------------------*/
int rop_zset_add(redisContext *conn, char* key, long score, char* member)
{
    int ret = 0;
    redisReply* reply = NULL;

    do
    {
        reply = redisCommand(conn, "ZADD %s %ld %s", key, score, member);

        if(reply->integer != 1)
        {
            LOG("[-][GMS_REDIS]ZADD: %s,member: %s Error:%s,%s\n", key, member, reply->str, conn->errstr);
            ret = -1;
            break;
        }
    }while(0);

    if(reply != NULL)
    {
        freeReplyObject(reply);
        reply = NULL;
    }

    return ret;
}

/* -------------------------------------------*/
/**
 * @brief  想一个hash表中添加一条 key-value 数据
 *
 * @param conn  redis连接
 * @param key   哈希表名
 * @param field
 * @param value
 *
 * @returns
 *            0        succ
 *            -1        FAIL
 */
/* -------------------------------------------*/
int rop_hash_set(redisContext *conn, char *key, char *fileid, char *value)
{
    int ret = 0;
    redisReply* reply = NULL;

    do
    {
        reply = redisCommand(conn, "hset %s %s %s", key, fileid, value);
        if(reply == NULL || reply->type != REDIS_REPLY_INTEGER)
        {
            LOG("[-][GMS_REDIS]hset %s %s %s error %s\n", key, fileid, value,conn->errstr);
            ret =  -1;
            break;
        }
    }while(0);

    freeReplyObject(reply);

    return ret;
}

/* -------------------------------------------*/
/**
 * @brief        删除指定的成员
 *
 *
 * @param conn       已建立的链接
 * @param key        zset表名
 * @param member     zset成员名
 *
 * @returns
 *            0            succ
 *            -1            fail
 */
/* -------------------------------------------*/
int rop_zset_zrem(redisContext *conn, char* key, char* member)
{
    int ret = 0;
    redisReply* reply = NULL;

    do
    {
        reply = redisCommand(conn, "ZREM %s %s", key, member);
        if(reply->integer != 1)
        {
            LOG("[-][GMS_REDIS]ZREM: %s,member: %s Error:%s,%s\n", key, member,reply->str, conn->errstr);
            ret = -1;
            break;
        }
    }while(0);

    if(reply != NULL)
    {
        freeReplyObject(reply);
        reply = NULL;
    }
    return ret;
}

/* -------------------------------------------*/
/**
 * @brief  从 key 指定的哈希集中移除指定的域(字段)
 *
 * @param conn  redis连接
 * @param key   哈希表名
 * @param field 字段
 *
 * @returns
 *            0        succ
 *            -1        FAIL
 */
/* -------------------------------------------*/
int rop_hash_del(redisContext *conn, char *key, char *fileid)
{
    int ret = 0;
    redisReply* reply = NULL;

    do
    {
        reply = redisCommand(conn, "hdel %s %s", key, fileid);
        if(reply->integer != 1)
        {
            LOG("[-][GMS_REDIS]hdel %s %s %s error %s\n", key, fileid,conn->errstr);
            ret =  -1;
            break;
        }
    }while(0);

    if(reply != NULL)
    {
        freeReplyObject(reply);
        reply = NULL;
    }

    return ret;
}

//转存文件
//返回值：0成功，-1转存失败，-2文件已存在
int save_file(char *user, char *md5, char *filename)
{
    int ret = 0;
    char sql_cmd[512] = {0};
    MYSQL* conn = NULL;
    char* out = NULL;
    int ret2 = 0;
    char tmp[512] = {0};

    do
    {
        conn = get_mysql(mysql_user, mysql_pwd, mysql_db);
        if(conn == NULL)
        {
            LOG("get_mysql error\n");
            ret = -1;
            break;
        }

        mysql_query(conn, "set names utf8mb4");

        sprintf(sql_cmd, "select * from user_file_list where user = '%s' and md5 = '%s' and file_name = '%s'", user, md5, filename);
        ret2 = process_result_one(conn, sql_cmd, NULL);
        if(ret2 == 2)
        {
            LOG("%s[filename:%s, md5:%s]已存在\n", user, filename, md5);
            ret = -2;
            break;
        }

        sprintf(sql_cmd, "select count from file_info where md5 = '%s'", md5);
        ret2 = process_result_one(conn, sql_cmd, tmp);
        if(ret2 != 0)
        {
            LOG("%s 操作失败: %s\n", sql_cmd, mysql_error(conn));
            ret = -1;
            break;
        }

        int count = atoi(tmp);
        sprintf(sql_cmd, "update file_info set count = %d where md5 = '%s'", count+1, md5);
        if(mysql_query(conn, sql_cmd) != 0)
        {
            LOG("%s 操作失败: %s\n", sql_cmd, mysql_error(conn));
            ret = -1;
            break;
        }

        struct timeval tv;
        struct tm* ptm;
        char time_str[128] = {0};

        gettimeofday(&tv, NULL);
        ptm = localtime(&tv.tv_sec);
        strftime(time_str, sizeof(time_str), "%Y-%m-%d %H:%M:%S", ptm);

        //sql语句
        /*
        -- =============================================== 用户文件列表
        -- user	文件所属用户
        -- md5 文件md5
        -- create_time 文件创建时间
        -- file_name 文件名字
        -- shared_status 共享状态, 0为没有共享， 1为共享
        -- pv 文件下载量，默认值为0，下载一次加1
        */

        sprintf(sql_cmd, "insert into user_file_list values('%s', '%s', '%s', '%s', %d, %d)", user, md5, time_str, filename, 0, 0);
        if(mysql_query(conn, sql_cmd) != 0)
        {
            LOG("%s 操作失败: %s\n", sql_cmd, mysql_error(conn));
            ret = -1;
            break;
        }

        sprintf(sql_cmd, "select count from user_file_count where user = '%s'", user);
        count = 0;
        ret2 = process_result_one(conn, sql_cmd, tmp);
        if(ret2 == 1)
        {
            sprintf(sql_cmd, "insert into user_file_count values('%s', %d)", user, 1);
        }
        else if(ret2 == 0)
        {
            count = atoi(tmp);
            sprintf(sql_cmd, "update user_file_count set count = %d where user = '%s'", count+1, user);
        }

        if(mysql_query(conn, sql_cmd) != 0)
        {
            LOG("%s 操作失败: %s\n", sql_cmd, mysql_error(conn));
            ret = -1;
            break;
        }
    }while(0);

    /*
    返回值：0成功，-1转存失败，-2文件已存在
    转存文件：
        成功：{"code":"020"}
        文件已存在：{"code":"021"}
        失败：{"code":"022"}
    */

    out = NULL;
    if(ret == 0)
    {
        out = return_status("020");
    }
    else if(ret == -1)
    {
        out = return_status("022");
    }
    else if(ret == -2)
    {
        out = return_status("021");
    }

    if(out != NULL)
    {
        printf(out);
        free(out);
        out = NULL;
    }

    if(conn != NULL)
    {
        mysql_close(conn);
        conn = NULL;
    }

    return ret;
}

//文件下载标志处理
int pv_file(char *md5, char *filename)
{
    int ret = 0;
    char sql_cmd[512] = {0};
    char* out = NULL;
    MYSQL* conn = NULL;
    int ret2 = 0;
    redisContext* redis_conn = NULL;
    char tmp[512] = {0};
    char fileid[1024] = {0};

    do
    {
        conn = get_mysql(mysql_user, mysql_pwd, mysql_db);
        if(conn == NULL)
        {
            LOG("get_mysql error\n");
            ret = -1;
            break;
        }

        mysql_query(conn, "set names utf8mb4");

        redis_conn = redisConnect(redis_ip, atoi(redis_port));
        if(redis_conn == NULL || redis_conn->err)
        {
            if(redis_conn)
            {
                LOG("%s\n", redis_conn->errstr);
            }
            else
            {
                LOG("redisConnect error\n");
            }
            ret = -1;
            break;
        }

        sprintf(sql_cmd, "select pv from share_file_list where md5 = '%s' and file_name = '%s'", md5, filename);
        ret2 = process_result_one(conn, sql_cmd, tmp);
        int pv = 0;
        if(ret2 == 0)
        {
            pv = atoi(tmp);
        }
        else
        {
            LOG("%s 操作失败: %s\n", sql_cmd, mysql_error(conn));
            ret = -1;
            break;
        }

        ret2 = rop_zset_exit(redis_conn, FILE_PUBLIC_ZSET, fileid);
        if(ret2 == 1)
        {
            ret = rop_zset_increment(redis_conn, FILE_PUBLIC_ZSET, fileid);
            if(ret != 0)
            {
                LOG("rop_zset_increment 操作失败\n");
            }
        }
        else if(ret2 == 0)
        {
            rop_zset_add(redis_conn, FILE_PUBLIC_ZSET, pv+1, fileid);

            rop_hash_set(redis_conn, FILE_NAME_HASH, fileid, filename);
        }
        else
        {
            ret = -1;
            break;
        }
    }while(0);

    out = NULL;
    if(ret == 0)
    {
        out = return_status("016");
    }
    else
    {
        out = return_status("017");
    }

    if(out != NULL)
    {
        printf(out);
        free(out);
        out = NULL;
    }

    if(redis_conn != NULL)
    {
        redisFree(redis_conn);
        redis_conn = NULL;
    }

    if(conn != NULL)
    {
        mysql_close(conn);
        conn = NULL;
    }

    return ret;
}

/* -------------------------------------------*/
/**
 * @brief		将指定的zset表，对应的成员，值自增1
 *				（key 或 成员不存在 则创建）
 *
 * @param conn		已建立的链接
 * @param key		zset表名
 * @param member	zset成员名
 *
 * @returns
 *			0			succ
 *			-1			fail
 */
/* -------------------------------------------*/
int rop_zset_increment(redisContext *conn, char* key, char* member)
{
    int ret = 0;
    redisReply* reply = NULL;

    do
    {
        reply = redisCommand(conn, "ZINCRBY %s 1 %s", key, member);
        if(strcmp(reply->str, "OK") != 0)
        {
            LOG("[-][GMS_REDIS]Add or increment table: %s,member: %s Error:%s,%s\n", key, member,reply->str, conn->errstr);
            ret = -1;
            break;
        }
    }while(0);

    if(reply != NULL)
    {
        freeReplyObject(reply);
        reply = NULL;
    }

    return ret;
}